/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"
#include "BTreeNode.h"
#include <list>
#include <set>

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning
  BTreeIndex indexfile;
  bool usesIndex;

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) 
  {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }

  // open the index file
  usesIndex = true;
  if ((rc = indexfile.open(table + ".idx", 'r')) < 0) 
  {
    fprintf(stderr, "Error: index %s does not exist\n", table.c_str());
    usesIndex = false;
  }

  if (usesIndex)
  {
      list<BTLeafPair*>* pairs = new list<BTLeafPair*>();

      // Find min and max of range and inequalities to skip
      int numInequalities = 0;
      int* minMaxInequalities = createKeyRanges(cond, numInequalities);
      if (minMaxInequalities)
      {
          int keyMin = minMaxInequalities[0];
          int keyMax = minMaxInequalities[1];

          // Move inequalities into a set
          set<int>* inequalities = new set<int>();
          for (int i = 0; i < numInequalities; ++i)
          {
              inequalities->insert(minMaxInequalities[2+i]);
          }
          
          delete[] minMaxInequalities;
      
          // Find location of first entry
          IndexCursor cursor;
          if ((rc = indexfile.locate(keyMin, cursor)) != 0)
          {
              fprintf(stderr, "Error: SqlEngine::select - could not locate cursor for minimum key\n");
              delete inequalities;
              return rc;
          }

          // Read all key, rid pairs until max key, skipping inequalities
          key = keyMin;
          while (key <= keyMax && cursor.pid != 0) 
          {
              BTLeafPair* pair = new BTLeafPair;
              if ((rc = indexfile.readForward(cursor, pair->key, pair->rid)) != 0)
              {
                  fprintf(stderr, "Error: SqlEngine::select - could not read forward from cursor with Pid %d and eid %d\n", 
                      cursor.pid, cursor.eid);
                  delete inequalities;
                  return rc;
              }
              key = pair->key;

              // Add to list only if not one of the inequalities and is in range
              if (inequalities->find(pair->key) ==  inequalities->end() && pair->key <= keyMax)
              {
                  pairs->push_back(pair);
              }
              else
              {
                  delete pair;
              }
          }
          
          

          // Clean up inequalities
          delete inequalities;
      }

      // Read values and output text
      count = 0;
      for (list<BTLeafPair*>::iterator pairIt = pairs->begin(); pairIt != pairs->end(); ++pairIt)
      {
          // Set RecordId
          rid.pid = (*pairIt)->rid.pid;
          rid.sid = (*pairIt)->rid.sid;

          // read the tuple
          if ((rc = rf.read(rid, key, value)) < 0) 
          {
              fprintf(stderr, "Error: SqlEngine::select - while reading a tuple from table %s\n", table.c_str());
              goto exit_select_index;
          }

          // Remove if the value doesn't match a condition
          for (unsigned i = 0; i < cond.size(); i++) 
          {
              // compute the difference between the tuple value and the condition value
              // Skip if the condition is concerned with key, already rooted those out
              if (cond[i].attr == 2) 
              {
                  diff = strcmp(value.c_str(), cond[i].value);
                  
                  // skip the tuple if any condition is not met
                  switch (cond[i].comp) 
                  {
                  case SelCond::EQ:
                      if (diff != 0) goto next_tuple_index;
                      break;
                  case SelCond::NE:
                      if (diff == 0) goto next_tuple_index;
                      break;
                  case SelCond::GT:
                      if (diff <= 0) goto next_tuple_index;
                      break;
                  case SelCond::LT:
                      if (diff >= 0) goto next_tuple_index;
                      break;
                  case SelCond::GE:
                      if (diff < 0) goto next_tuple_index;
                      break;
                  case SelCond::LE:
                      if (diff > 0) goto next_tuple_index;
                      break;
                  }
              }
          }

          // the condition is met for the tuple. 
          // increase matching tuple counter
          count++;
          
          // print the tuple 
          switch (attr) 
          {
          case 1:  // SELECT key
              fprintf(stdout, "%d\n", key);
              break;
          case 2:  // SELECT value
              fprintf(stdout, "%s\n", value.c_str());
              break;
          case 3:  // SELECT *
              fprintf(stdout, "%d '%s'\n", key, value.c_str());
              break;
          }

          // move to the next tuple
next_tuple_index:
          ;
      }

      // print matching tuple count if "select count(*)"
      if (attr == 4) 
      {
          fprintf(stdout, "%d\n", count);
      }
      rc = 0;

exit_select_index:
      // Clean up dynamic allocations and open files
      if (pairs)
      {
          for (list<BTLeafPair*>::iterator pairIt = pairs->begin(); pairIt != pairs->end(); ++pairIt)
          {
              BTLeafPair* pair = *pairIt;
              if (pair)
                  delete pair;
          }

          delete pairs;
      }
      indexfile.close();
      rf.close();
  }
  else
  {
      // scan the table file from the beginning
      rid.pid = rid.sid = 0;
      count = 0;
      while (rid < rf.endRid()) 
      {
        // read the tuple
        if ((rc = rf.read(rid, key, value)) < 0) 
        {
          fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
          goto exit_select;
        }

        // check the conditions on the tuple
        for (unsigned i = 0; i < cond.size(); i++) 
        {
          // compute the difference between the tuple value and the condition value
          switch (cond[i].attr) 
          {
          case 1:
              diff = key - atoi(cond[i].value);
              break;
          case 2:
              diff = strcmp(value.c_str(), cond[i].value);
              break;
          }

          // skip the tuple if any condition is not met
          switch (cond[i].comp) 
          {
          case SelCond::EQ:
              if (diff != 0) goto next_tuple;
              break;
          case SelCond::NE:
              if (diff == 0) goto next_tuple;
              break;
          case SelCond::GT:
              if (diff <= 0) goto next_tuple;
              break;
          case SelCond::LT:
              if (diff >= 0) goto next_tuple;
              break;
          case SelCond::GE:
              if (diff < 0) goto next_tuple;
              break;
          case SelCond::LE:
              if (diff > 0) goto next_tuple;
              break;
          }
        }

        // the condition is met for the tuple. 
        // increase matching tuple counter
        count++;

        // print the tuple 
        switch (attr) 
        {
        case 1:  // SELECT key
            fprintf(stdout, "%d\n", key);
            break;
        case 2:  // SELECT value
            fprintf(stdout, "%s\n", value.c_str());
            break;
        case 3:  // SELECT *
            fprintf(stdout, "%d '%s'\n", key, value.c_str());
            break;
        }

        // move to the next tuple
        next_tuple:
        ++rid;
      }

      // print matching tuple count if "select count(*)"
      if (attr == 4) 
      {
          fprintf(stdout, "%d\n", count);
      }
      rc = 0;

      // close the table file and return
      exit_select:
      rf.close();
  }

  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
  RecordFile rf;
  BTreeIndex indexfile;
  RecordId rid;
  ifstream inputfile;
  char line[1024];
  int key;
  string value;
  RC rc = 0;

  // Open load file
  inputfile.open(loadfile.c_str());
  if (inputfile.fail()) 
  {
      fprintf(stderr, "Error: SqlEngine::load - Could not open file %s\n", loadfile.c_str());
      return RC_FILE_OPEN_FAILED;
  }
  
  // Open table file, create if doesn't exist
  if (rc = rf.open(table + ".tbl", 'w') < 0) 
  {
      fprintf(stderr, "Error: SqlEngine::load - Could not open/create table file for %s\n", table.c_str());
      inputfile.close();
      return rc;
  }

  // Open index file if indicated, create if doesn't exist
  if (index)
  {
      if (rc = indexfile.open(table + ".idx", 'w') < 0) 
      {
          fprintf(stderr, "Error: SqlEngine::load - Could not open/create index file for %s\n", table.c_str());
          inputfile.close();
          rf.close();
          return rc;
      }
  }

  // Parse lines from load file, append key value pairs to table
  while (inputfile.getline(line, 1024)) 
  {
      if (rc = parseLoadLine(line, key, value) < 0)
      {
          fprintf(stderr, "Error: SqlEngine::load - Could not parse line %s\n", line);
          rf.close();
          inputfile.close();
          if (index) { indexfile.close(); }
          return rc;
      }
      if (rc = rf.append(key, value, rid) < 0)
      {
          rf.close();
          inputfile.close();
          return rc;
      }
      if (index)
      {
          if ((rc = indexfile.insert(key, rid)) < 0)
          {
              fprintf(stderr, "Error: SqlEngine::load - Could not insert key %d to index %s\n", key, table.c_str());
              rf.close();
              inputfile.close();
              indexfile.close();
              return rc;
          }
      }
  }

  rc = rf.close();
  inputfile.close();
  if (index) { indexfile.close(); }

  return rc;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}

int* SqlEngine::createKeyRanges(const vector<SelCond>& conds, int& numInequalities)
{
    int min = INT_MIN;
    int max = INT_MAX;

    set<int>* notEqualTo = new set<int>();
    vector<SelCond>::const_iterator iterator = conds.begin();
    while (iterator < conds.end())
    {
        SelCond cond = *iterator;
        int value = atoi(cond.value);

        // Only look at condition if dealing with the key
        if (cond.attr == 1)
        {
            switch (cond.comp)
            {
            case SelCond::EQ:
                // Return nothing if must be equal to key outside bounds
                if (value < min || value > max)
                {
                    delete notEqualTo;
                    return NULL;
                }
                min = value;
                max = value;
                break;
            case SelCond::NE:
                notEqualTo->insert(value);
                break;
            case SelCond::GE:
                if (min < value)
                    min = value;
                break;
            case SelCond::LE:
                if (max > value)
                    max = value;
                break;
            case SelCond::GT:
                if (min <= value)
                    min = value + 1;
                break;
            case SelCond::LT:
                if (max >= value)
                    max = value - 1;
                break;
            } // end switch

            if (min > max)
            {
                delete notEqualTo;
                return NULL;
            }
        }

        iterator++;
    }

    int* minMaxInequalities = new int[notEqualTo->size()];
    int i = 0;
    for (set<int>::iterator inequalityIt = notEqualTo->begin(); inequalityIt != notEqualTo->end(); ++inequalityIt)
    {
        minMaxInequalities[2+i] = *inequalityIt;
        i++;
    }

    minMaxInequalities[0] = min;
    minMaxInequalities[1] = max;
    numInequalities = notEqualTo->size();

    delete notEqualTo;

    return minMaxInequalities;
}
